using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

namespace Oops.Xna.Framework.GameState
{
	/// <summary>
	/// A game component that manages <see cref="GameScreen"/> instances.
	/// </summary>
	public class GameStateComponent : DrawableGameComponent, IGameStateService, IInternalGameStateService
	{
		private bool initialized;
		private List<IGameComponent> notYetInitialized = new List<IGameComponent>();

		private GameScreen activeScreen;

		private GameScreen previousScreen;
		private GameScreen currentScreen;
		private GameScreenCollection screens;

		/// <summary>
		/// Initializes a new instance of the <see cref="GameStateComponent"/> class.
		/// </summary>
		/// <param name="game">The <see cref="Microsoft.Xna.Framework.Game"/>that the game component should be attached to.</param>
		public GameStateComponent(Game game) : base(game)
		{
			// The game parameter must be specified.
			if (game == null)
			{
				throw new ArgumentNullException("game");
			}

			// Create the game screen collection and subscribe to some of it's events.
			this.screens = new GameScreenCollection();
			this.screens.ComponentAdded += new EventHandler<GameComponentCollectionEventArgs<GameScreen>>(this.OnGameScreenAdded);
			this.screens.ComponentRemoved += new EventHandler<GameComponentCollectionEventArgs<GameScreen>>(this.OnGameScreenRemoved);
		}

		#region GameComponent Members

		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				lock (this)
				{
					// Remove self from the service container.
					if (this.Game.Services.GetService(typeof(IGameStateService)) == this)
					{
						this.Game.Services.RemoveService(typeof(IGameStateService));
						this.Game.Services.RemoveService(typeof(IInternalGameStateService));
					}

					// Copy the game screens to an array for index-based iteration.
					GameScreen[] screens = new GameScreen[this.screens.Count];
					this.screens.CopyTo(screens, 0);

					// Dispose of all game screens.
					for (int i = 0; i < screens.Length; i++)
					{
						IDisposable disposable = screens[i] as IDisposable;
						if (disposable != null)
						{
							this.activeScreen = screens[i];
							disposable.Dispose();
						}
					}
				}
			}

			// Call inherited method.
			base.Dispose(disposing);
		}

		#endregion

		#region IGameComponent Members

		public override void Initialize()
		{
			// Add self to the service container.
			this.Game.AddService<IGameStateService>(this, true);
			this.Game.AddService<IInternalGameStateService>(this, true);

			// Initialize all necessary behaviors.
			while (this.notYetInitialized.Count != 0)
			{
				this.activeScreen = this.notYetInitialized[0] as GameScreen;
				this.notYetInitialized[0].Initialize();
				this.notYetInitialized.RemoveAt(0);
			}

			// The first screen added will be the current screen.
			if (this.screens.Count > 0)
			{
				this.currentScreen = this.screens[0];
				GameScreen previousScreen = null;

				this.activeScreen = this.currentScreen;
				this.currentScreen.Enter(new GameTime(TimeSpan.Zero, TimeSpan.Zero, TimeSpan.Zero, TimeSpan.Zero), ref previousScreen);
			}

			// Call inherited method.
			base.Initialize();

			// Set the initialized flag.
			this.initialized = true;
		}

		#endregion

		#region IUpdateable

		public override void Update(GameTime gameTime)
		{
			// Initialize the new screen.
			GameScreen newScreen = null;
			
			// Get the current state of the previous screen and determine if it needs updated.  
			// The previous screen's new screen result is irrelevant.
			if (this.previousScreen != null)
			{
				this.activeScreen = this.previousScreen;
				if (this.previousScreen.GetTransition(gameTime, ref newScreen) && this.previousScreen.Enabled)
				{
					this.previousScreen.Update(gameTime);
				}
			}

			// Get the current state of the current screen and determine if it needs updated.  
			if (this.currentScreen != null)
			{
				newScreen = null;
				this.activeScreen = this.currentScreen;
				if (this.currentScreen.GetTransition(gameTime, ref newScreen))
				{
					// If a new screen is specified, a transition is occurring.
					if (newScreen != null)
					{
						// Notify the current screen that it's exiting.
						if (this.currentScreen != null)
						{
							this.currentScreen.Exit(gameTime);
						}

						// The new screen is now the current screen.
						this.previousScreen = this.currentScreen;
						this.currentScreen = newScreen;

						// Notify the new screen that it's starting.  Make sure the content is loaded.
						this.activeScreen = newScreen;
						newScreen.Enter(gameTime, ref this.previousScreen);
					}

					// Update the current screen.
					if (this.currentScreen.Enabled)
					{
						this.activeScreen = this.currentScreen;
						this.currentScreen.Update(gameTime);
					}
				}
			}

			// Call inherited method.
			base.Update(gameTime);
		}

		#endregion 

		#region IDrawable

		public override void Draw(GameTime gameTime)
		{
			// Draw the previous screen, if necessary.
			if (this.previousScreen != null && this.previousScreen.Visible)
			{
				this.activeScreen = this.previousScreen;
				this.previousScreen.Draw(gameTime);
			}

			// Draw the current screen, if necessary.
			if (this.currentScreen != null && this.currentScreen.Visible)
			{
				this.activeScreen = this.currentScreen;
				this.currentScreen.Draw(gameTime);
			}

			// Call inherited method.
			base.Draw(gameTime);
		}

		#endregion

		#region IGameStateService Members

		public GameScreen CurrentScreen
		{
			get { return this.currentScreen; }
		}

		public GameScreen PreviousScreen
		{
			get { return this.previousScreen; }
		}

		public GameScreenCollection Screens
		{
			get { return this.screens; }
		}

		#endregion

		#region IInternalGameStateService Members

		GameScreen IInternalGameStateService.ActiveScreen
		{
			get { return this.activeScreen; }
		}

		#endregion

		private void OnGameScreenAdded(object sender, GameComponentCollectionEventArgs<GameScreen> e)
		{
			// Initialize or add the game screen to the initialization pending list.
			if (this.initialized)
			{
				this.activeScreen = e.Item;
				e.Item.Initialize();
			}
			else
			{
				this.notYetInitialized.Add(e.Item);
			}
		}

		private void OnGameScreenRemoved(object sender, GameComponentCollectionEventArgs<GameScreen> e)
		{
			// Remove the game screen from the initialization pending list.
			if (!this.initialized)
			{
				this.notYetInitialized.Remove(e.Item);
			}
		}
	}
}

